package Rangel.common.world.helper;

import Rangel.common.RangelConstants;
import Rangel.common.pathplanning.astar.graph.GraphModule;
import Rangel.common.pathplanning.astar.graph.MyEdge;
import Rangel.common.pathplanning.astar.graph.Node;
import Rangel.common.world.entity.*;
import Rangel.common.world.tools.Util;
import adf.agent.develop.DevelopData;
import adf.agent.info.AgentInfo;
import adf.agent.info.ScenarioInfo;
import adf.agent.info.WorldInfo;
import adf.agent.module.ModuleManager;
import rescuecore2.misc.Pair;
import rescuecore2.standard.entities.*;
import rescuecore2.worldmodel.EntityID;

import java.util.*;

public class RoadHelper implements IHelper {
    private RangelWorldHelper worldHelper;
    protected ScenarioInfo scenarioInfo;
    protected AgentInfo agentInfo;
    protected WorldInfo worldInfo;
    protected ModuleManager moduleManager;
    protected DevelopData developData;
    private Map<EntityID, EntityID> pathIdMap;


    public RoadHelper(RangelWorldHelper worldHelper, AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) {
        this.worldHelper = worldHelper;
        this.worldInfo = wi;
        this.agentInfo = ai;
        this.scenarioInfo = si;
        this.moduleManager = moduleManager;
        this.developData = developData;
        pathIdMap = new HashMap<>();
    }

    public EntityID getPathId(EntityID id) {
        return pathIdMap.get(id);
    }//获取路径ID

    public void setPathId(EntityID roadId, EntityID pathId) {
        pathIdMap.put(roadId, pathId);
    }//

    public void init() {

    }

    private int lastUpdateTime = -1;//最后更新时间


    @Override
    public void update() {
        if (agentInfo.me() instanceof PoliceForce) {//如果是警察时

        } else if (lastUpdateTime < agentInfo.getTime()) {
            lastUpdateTime = agentInfo.getTime();
            Set<Road> roadsSeen = worldHelper.getRoadsSeen();
            Collection<StandardEntity> roads = worldHelper.getRoads();
            for (StandardEntity entity : roads) {
                RangelRoad RangelRoad = getRangelRoad(entity.getID());
                if (roadsSeen.contains(entity)) {
                    updatePassably(RangelRoad);
                } else {
                    RangelRoad.resetOldPassably();
                }
            }
        }
    }

    public List<Building> getBuildingsOfThisEntrance(EntityID entrance) {
        int loop = 0;
        List<Building> buildings = new ArrayList<>();
        List<Area> neighbours = new ArrayList<>();
        Area tempArea;
        Area neighbour;
        neighbours.add((Area) worldInfo.getEntity(entrance));

        while (!neighbours.isEmpty() && loop < 20) {
            loop++;
            tempArea = neighbours.get(0);
            neighbours.remove(0);

            for (EntityID entityID : tempArea.getNeighbours()) {
                neighbour = (Area) worldInfo.getEntity(entityID);
                if (neighbour instanceof Building) {
                    if (!buildings.contains(neighbour)) {
                        buildings.add((Building) neighbour);
                        neighbours.add(neighbour);
                    }
                }
            }
        }
        return buildings;
    }

    public void updatePassably(RangelRoad RangelRoad) {
        for (int i = 0; i < RangelRoad.getRangelEdges().size() - 1; i++) {
            RangelEdge edge1 = RangelRoad.getRangelEdges().get(i);
            if (!edge1.isPassable()) {
                continue;
            }
            for (int j = i + 1; j < RangelRoad.getRangelEdges().size(); j++) {
                RangelEdge edge2 = RangelRoad.getRangelEdges().get(j);
                if (!edge2.isPassable()) {
                    continue;
                }
                setMyEdgePassably(RangelRoad, edge1, edge2, isPassable(RangelRoad, edge1, edge2, false));
            }
        }
    }

    public boolean isPassable(EntityID roadId) {
        RangelRoad RangelRoad = worldHelper.getRangelRoad(roadId);
        return RangelRoad.isPassable();
    }

    public boolean isPassable(RangelRoad RangelRoad, RangelEdge from, RangelEdge to, boolean hardWalk) {
        if (!from.getNeighbours().first().equals(to.getNeighbours().first())) {
            System.err.println("this 2 edge is not in a same area!!!");
            return false;
        }
        if (hardWalk ? from.isAbsolutelyBlocked() || to.isAbsolutelyBlocked() : from.isBlocked() || to.isBlocked())
            return false;
        Pair<List<RangelEdge>, List<RangelEdge>> edgesBetween = getEdgesBetween(RangelRoad, from, to, false);

        int count = RangelRoad.getRangelBlockades().size();
        List<RangelEdge> blockedEdges = new ArrayList<>();
        if (count == 1) {
            blockedEdges.addAll(RangelRoad.getRangelBlockades().get(0).getBlockedEdges());
        } else if (count > 1) {
            for (int i = 0; i < count - 1; i++) {
                RangelBlockade block1 = RangelRoad.getRangelBlockades().get(i);
                for (int j = i + 1; j < count; j++) {
                    RangelBlockade block2 = RangelRoad.getRangelBlockades().get(j);
                    if (isBlockedTwoSides(block1, edgesBetween)) {
                        return false;
                    }
                    if (isBlockedTwoSides(block2, edgesBetween)) {
                        return false;
                    }
                    if (isInSameSide(block1, block2, edgesBetween)) {
                        continue;
                    }
                    if (Util.isPassable(block1.getPolygon(), block2.getPolygon(), hardWalk ? RangelConstants.AGENT_MINIMUM_PASSING_THRESHOLD : RangelConstants.AGENT_PASSING_THRESHOLD)) {
                        blockedEdges.removeAll(block1.getBlockedEdges());
                        blockedEdges.addAll(block1.getBlockedEdges());
                        blockedEdges.removeAll(block2.getBlockedEdges());
                        blockedEdges.addAll(block2.getBlockedEdges());
                    }
                }
            }
        } else {
            return !(from.isBlocked() || to.isBlocked());
        }
        return !(Util.containsEach(blockedEdges, edgesBetween.first()) && Util.containsEach(blockedEdges, edgesBetween.second()));
    }


    private boolean isInSameSide(RangelBlockade block1, RangelBlockade block2, Pair<List<RangelEdge>, List<RangelEdge>> edgesBetween) {
        return edgesBetween.first().containsAll(block1.getBlockedEdges()) &&
                edgesBetween.first().containsAll(block2.getBlockedEdges()) ||
                edgesBetween.second().containsAll(block1.getBlockedEdges()) &&
                        edgesBetween.second().containsAll(block2.getBlockedEdges());

    }

    private boolean isBlockedTwoSides(RangelBlockade block1, Pair<List<RangelEdge>, List<RangelEdge>> edgesBetween) {
        return Util.containsEach(edgesBetween.first(), block1.getBlockedEdges()) &&
                Util.containsEach(edgesBetween.second(), block1.getBlockedEdges());
    }

    private void setMyEdgePassably(RangelRoad road, RangelEdge edge1, RangelEdge edge2, boolean passably) {//设置边缘可通过
        if (!(agentInfo.me() instanceof Human) || !edge1.getNeighbours().first().equals(edge2.getNeighbours().first())) {
            return;
        }

        GraphModule graph = getGraph();
        Node node1 = graph.getNode(edge1.getMiddle());
        Node node2 = graph.getNode(edge2.getMiddle());
        MyEdge myEdge = graph.getMyEdge(road.getParent().getID(), new Pair<>(node1, node2));
        if (passably) {
            road.addReachableEdges(edge1, edge2);
        } else {
            road.removeReachableEdges(edge1, edge2);
        }
        myEdge.setPassable(passably);
    }

    private GraphModule getGraph() {
        return worldHelper.getGraph();
    }

    public static Pair<List<RangelEdge>, List<RangelEdge>> getEdgesBetween(RangelRoad road, RangelEdge edge1, RangelEdge edge2, boolean justImPassable) {
        List<RangelEdge> leftSideEdges = new ArrayList<>();
        List<RangelEdge> rightSideEdges = new ArrayList<>();
        rescuecore2.misc.geometry.Point2D startPoint1 = edge1.getStart();
        rescuecore2.misc.geometry.Point2D endPoint1 = edge1.getEnd();
        rescuecore2.misc.geometry.Point2D startPoint2 = edge2.getStart();
        rescuecore2.misc.geometry.Point2D endPoint2 = edge2.getEnd();

        boolean finishedLeft = false;
        boolean finishedRight = false;
        for (RangelEdge ignored : road.getRangelEdges()) {
            if (finishedLeft && finishedRight)
                break;
            for (RangelEdge ed : road.getRangelEdges()) {
                if (finishedLeft && finishedRight)
                    break;
                if (ed.equals(edge1) || ed.equals(edge2)) {
                    continue;
                }
                if (startPoint1.equals(startPoint2) || startPoint1.equals(endPoint2)) {
                    finishedLeft = true;
                }
                if (endPoint1.equals(startPoint2) || endPoint1.equals(endPoint2)) {
                    finishedRight = true;
                }

                if (ed.getStart().equals(startPoint1) && !finishedLeft && !leftSideEdges.contains(ed)) {
                    startPoint1 = ed.getEnd();
                    if (!justImPassable || !ed.isPassable())
                        leftSideEdges.add(ed);
                    continue;
                }
                if (ed.getEnd().equals(startPoint1) && !finishedLeft && !leftSideEdges.contains(ed)) {
                    startPoint1 = ed.getStart();
                    if (!justImPassable || !ed.isPassable())
                        leftSideEdges.add(ed);
                    continue;
                }
                if (ed.getStart().equals(endPoint1) && !finishedRight && !rightSideEdges.contains(ed)) {
                    endPoint1 = ed.getEnd();
                    if (!justImPassable || !ed.isPassable())
                        rightSideEdges.add(ed);
                    continue;
                }
                if (ed.getEnd().equals(endPoint1) && !finishedRight && !rightSideEdges.contains(ed)) {
                    endPoint1 = ed.getStart();
                    if (!justImPassable || !ed.isPassable())
                        rightSideEdges.add(ed);
                }
            }
        }
        return new Pair<>(leftSideEdges, rightSideEdges);
    }

    public RangelRoad getRangelRoad(EntityID roadId) {
        return worldHelper.getRangelRoad(roadId);
    }

    public static Set<rescuecore2.standard.entities.Edge> getEdgesBetween(Area area1, Area area2) {
        Set<rescuecore2.standard.entities.Edge> edgesBetween = new HashSet<>();
        if (!area1.getNeighbours().contains(area2.getID()))
            return edgesBetween;
        for (rescuecore2.standard.entities.Edge edge : area1.getEdges()) {
            if (edge.isPassable() && edge.getNeighbour().equals(area2.getID())) {
                edgesBetween.add(edge);
            }
        }
        return edgesBetween;
    }

    public boolean isOpenOrNotSeen(EntityID buildingID, EntityID roadEntrance) {
        RangelRoad RangelRoad = worldHelper.getRangelRoad(roadEntrance);
        RangelBuilding RangelBuilding = worldHelper.getRangelBuilding(buildingID);
        if (RangelBuilding == null || RangelRoad == null) {
            return false;
        }
        if (RangelRoad.notSeen())
            return true;

        HashSet<RangelEdge> RangelEdges;
        List<RangelEdge> toRemove = new ArrayList<>();
        for (RangelEdge RangelEdge : RangelRoad.getRangelEdgesTo(buildingID)) {
            RangelEdges = new HashSet<>(RangelRoad.getReachableEdges(RangelEdge));
            RangelEdges.removeAll(RangelRoad.getRangelEdgesTo(buildingID));
            for (Entrance entrance : RangelBuilding.getEntrances()) {
                for (RangelEdge neighbourEdge : RangelEdges) {
                    if (neighbourEdge.getNeighbours().second().equals(entrance.getNeighbour().getID()) || neighbourEdge.isOtherSideBlocked(worldInfo)) {
                        toRemove.add(neighbourEdge);
                    }
                }
            }
            RangelEdges.removeAll(toRemove);
            if (!RangelEdge.isBlocked() && RangelEdges.size() > 0) {
                return true;
            }
        }
        return false;
    }

    public static List<Building> getConnectedBuildings(WorldInfo world, Road road) {
        List<Building> buildings = new ArrayList<>();
        StandardEntity standardEntity;

        for (EntityID id : road.getNeighbours()) {
            standardEntity = world.getEntity(id);
            if ((standardEntity instanceof Building) && !buildings.contains(standardEntity)) {
                buildings.add((Building) standardEntity);
            }
        }
        return buildings;
    }

    public ArrayList<Road> getConnectedRoads(EntityID id) {
        ArrayList<Road> neighbours = new ArrayList<>();

        Area area = (Area) worldInfo.getEntity(id);

        for (EntityID entityID : area.getNeighbours()) {
            StandardEntity standardEntity = worldInfo.getEntity(entityID);
            if (standardEntity instanceof Road) {
                neighbours.add((Road) standardEntity);
            }
        }

        return neighbours;
    }
}
